package com.kusiri.kframework.salesforce.ui
{
	import com.kusiri.kframework.KfApplicationLocator;
	import com.kusiri.kframework.salesforce.model.ISObject;
	import com.kusiri.kframework.salesforce.model.KfSalesforceModel;
	import com.salesforce.results.DescribeLayout;
	import com.salesforce.results.DescribeLayoutComponent;
	import com.salesforce.results.DescribeLayoutItem;
	import com.salesforce.results.DescribeLayoutResult;
	import com.salesforce.results.DescribeLayoutRow;
	import com.salesforce.results.DescribeLayoutSection;
	import com.salesforce.results.Fault;
	import com.salesforce.results.Field;
	import com.salesforce.results.RecordTypeMapping;
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	import mx.collections.ArrayCollection;
	import mx.containers.Form;
	import mx.containers.Grid;
	import mx.containers.GridItem;
	import mx.containers.GridRow;
	import mx.containers.HBox;
	import mx.containers.Panel;
	import mx.containers.VBox;
	import mx.controls.Alert;
	import mx.controls.ComboBox;
	import mx.controls.DateField;
	import mx.controls.Label;
	import mx.controls.Text;
	import mx.controls.TextArea;
	import mx.controls.TextInput;
	import mx.core.UIComponent;
	import mx.events.CloseEvent;
	import mx.events.FlexEvent;
	
	/**
	 * Automatic form creation for sobjects
	 */
	public class SObjectForm extends Form
	{		
		public var model : KfSalesforceModel;
		
		private var _controls : Array;
		private var _editable : Boolean = false;

		private var _container : VBox;
		private var _statusMessage : Text;

		private var _sobject:ISObject;
		private static var formUtil:SObjectFormUtil = new SObjectFormUtil();
		public var onSave:Function;
		public var onDelete:Function;
		public var onCancel:Function;
		public var layoutResult:DescribeLayoutResult;
		public var buttonsOnly:Boolean;
		private var buttons:ISObjectFormButtonBar;
		private var _buttonsClass : Class;
		private var _buttonsProperties : Object;
		
        private var _formIsValid:Boolean = false;
		
		private var nonNillableControls : Array;
		private var nonNillableLabels : Array;
		
		public function SObjectForm(layout:DescribeLayoutResult, sobject:ISObject
		, onSave:Function = null, onCancel:Function = null, buttonsClass:Class = null, buttonsProperties:Object = null, buttonsOnly:Boolean = false, edit:Boolean = false)
		{
			super();
			
			sobject.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, redrawForm, false, 0, true);
			
			_sobject = sobject;
			label = _sobject.__displayName;
			
			this.onSave = onSave;
			this.onCancel = onCancel;
			
			_buttonsClass = buttonsClass;
			_buttonsProperties = buttonsProperties;
			
			this.layoutResult = layout;
			this.buttonsOnly = buttonsOnly;
			
			redrawForm();
			
			if (edit)
			{
				addEventListener(FlexEvent.CREATION_COMPLETE, 
				function(event:Event) : void 
				{ 
					editable = true; 
				}, false, 0, true );
			}
		}

		/**
		 * @param buttonsClass    Should implement ISObjectFormButtonBar
		 * @param buttonsProperties  Properties to pass through to the buttons
		 */
		public function setButtons(buttonsClass:Class, buttonsProperties:Object) : void
		{
			_buttonsClass = buttonsClass;
			_buttonsProperties = buttonsProperties;
			redrawButtons();
		}


		[Bindable]
		public function get formIsValid() : Boolean
		{
			return _formIsValid;
		}

		public function set formIsValid(value:Boolean) : void
		{
			_formIsValid = value;
		} 

		public function set editable(value:Boolean):void
		{
			// not using a bind here
			if (buttons!=null) buttons.editable = value;
			
			//TODO: improve this
			
			_editable=value;
			
			redrawForm();
			
			var i:int=0;
			var j:int=nonNillableLabels.length;
			
			if (value)
			{
				for (;i<j;i++)
				{
					highlightLabel(nonNillableLabels[i]);		
				}
			}
			else
			{
				for (;i<j;i++)
				{
					normalLabel(nonNillableLabels[i]);		
				}
			}
		}

		public function get editable() : Boolean
		{
			return _editable;
		}

		public function get sobject() : ISObject
		{
			return _sobject;
		}
		
		[Bindable]
		public function set sobject(sobject:ISObject) : void
		{
			this.label = sobject.__displayName;
			
			_sobject = sobject;
			
			redrawForm();

		}
		
		private function normalLabel( label:Label) : void
		{
			label.setStyle('color', null);
		}
		
		private function highlightLabel( label:Label) : void
		{
			label.setStyle('color', '#FF0000');
		}	
		
		/**
		 * This is called whenever the sobject is changed
		 * This needs to be refactored into methods
		 */ 
		public function redrawForm(event : Event = null) : void 
		{
			_statusMessage = null;
			
			_controls = new Array();
			nonNillableControls = new Array();
			nonNillableLabels = new Array();
			
			removeAllChildren();
			
			if ( layoutResult == null || _sobject == null  ) return;
			
			if (buttonsOnly)
			{
				redrawButtons();
				return;
			}
			
			_container = new VBox();
			
			
			addChild(_container);
			maximizeWidth(_container);
			
			// can put in a lyout selector
			var layoutSelectorRequired:Boolean = layoutResult.recordTypeSelectorRequired;
			var layoutId:String;
			var defaultRecordTypeMapping:RecordTypeMapping;
			var mappings:Object = new Object();
			
			for (var i:int=0, j:int=layoutResult.recordTypeMappings.length; i<j; i++) 
			{
				var mapping:RecordTypeMapping = layoutResult.recordTypeMappings[i];
				
				//if (mapping.recordTypeId != sobject.recordType || ! mapping.available) continue;
				if ( ! mapping.available) continue;
				
				if (mapping.defaultRecordTypeMapping)
				{
					defaultRecordTypeMapping = mapping;
				}
				
				mappings[mapping.layoutId] = mapping;
				
			}
			
			// TODO: I havne't implemented the record type selector
			
			var usedMapping:RecordTypeMapping = defaultRecordTypeMapping;
			var layout:DescribeLayout;
			for (i=0, j=layoutResult.layouts.length; i<j; i++)
			{
				layout = layoutResult.layouts[i];
				if (layout.id == usedMapping.layoutId) break;
			}
			
			var layoutSections:ArrayCollection = editable ? layout.editLayoutSections : layout.detailLayoutSections ;
			
			for (i=0, j=( layoutSections ? layoutSections.length : 0 ); i<j; i++)
			{
				var section:DescribeLayoutSection = layoutSections[i];
				var panel:Panel = new Panel();
				panel.setStyle('paddingLeft', 5);
				panel.setStyle('paddingRight', 5);
				panel.setStyle('paddingTop', 5);
				panel.setStyle('paddingBottom', 5);
				_container.addChild(panel);
				panel.title = section.heading;
				maximizeWidth(panel);
				
				var grid:Grid = new Grid();
				maximizeWidth(grid);
				
				panel.addChild(grid);
				
				for (var k:int=0,l:int=section.layoutRows.length; k<l; k++)
				{
					var gRow:GridRow = new GridRow();
					maximizeWidth(gRow);
					grid.addChild(gRow);
					var dRow:DescribeLayoutRow = section.layoutRows[k];
					for(var m:int=0,n:int=dRow.layoutItems.length; m<n; m++)
					{
						

						var dItem:DescribeLayoutItem = dRow.layoutItems[m];
						
						// label
						var gItem:GridItem = new GridItem();
						var label:Label = new Label();
						label.text = dItem.label;
						normalLabel(label);
						gItem.addChild(label);
						gRow.addChild(gItem);
						
						// content
						gItem = new GridItem();
						maximizeWidth(gItem);
						gRow.addChild(gItem);
						
						
						if ( ! dItem.layoutComponents ) 
						{
							continue;
						}
						
						var children:Array = [];
						
						for (var o:int=0,p:int=dItem.layoutComponents.length; o<p; o++)
						{
							var dComponent:DescribeLayoutComponent = dItem.layoutComponents[o];
							
							if (dComponent.type == 'Separator')
							{
								var text:Text = new Text();
								text.text = dComponent.value;
								children.push(text);
								continue;
							}
							
							// the name of the salesforce field
							var fieldName:String = dComponent.value;
							var field:Field = _sobject.__sfDescription.fields[fieldName];
							
							var fieldEditable:Boolean = dItem.editable && (_sobject.Id == null && field.createable) || (_sobject.Id != null && field.updateable);
							
							var control: UIComponent;
							
							if (editable && fieldEditable)
							{
								
								
								control = formUtil.getFormField(sobject, fieldName, field, usedMapping) ;
								control.addEventListener(FlexEvent.VALUE_COMMIT, validateForm);
								
								if (!field.nillable || dItem.required)
								{
									nonNillableControls.push(control);
									nonNillableLabels.push(label);
								}
								
								_controls.push(control);
							}
							else
							{
								control = formUtil.getReadOnlyFormField(sobject, fieldName, field);
							}
							
							children.push(control);
							
							
						}
					
						// to seperate or not to seperate, that is the question
						if (children.length == 1)
						{
							gItem.addChild(children[0]);
							if (fieldEditable) maximizeWidth(children[0]);
						}
						else
						{
							if (editable)
							{
								var hbox:HBox = new HBox();
								//maximizeWidth(hbox);
								for(i=0,j=children.length; i<j; i++)
								{
									hbox.addChild(children[i]);
								}
								gItem.addChild(hbox);
							}
							else
							{
								text = new Text();
								text.text = children.map( function (element:*, index:int, arr:Array):String { return element.text } ).join(' ');
								// TODO : stylise
								text.setStyle('color','#000000');
								gItem.addChild(text);
							}
						}
						
					}
				}
				
			}
			
			redrawButtons();
			
		}
	
		protected function inPlaceEdit(e:MouseEvent) : void
		{
			var formControl:UIComponent = e.currentTarget as UIComponent;
			var fieldName:String = formControl.name;
			Alert.show('Edit '+fieldName);
		}
	
		protected function redrawButtons() : void
		{
			if (_buttonsClass)
			{
				buttons = new _buttonsClass();
				if (_buttonsProperties)
				{
					for (var prop:String in _buttonsProperties)
					{
						buttons[prop] = _buttonsProperties[prop];
					}
				}
			}
			else
			{
				// default buttons
				var buttons:SObjectFormButtonBar = new SObjectFormButtonBar();
				buttons.editClick = editForm;
				buttons.cancelClick = cancelForm;
				buttons.deleteClick = deleteAlert;
				buttons.saveClick = save;
				this.buttons = buttons;
			}
			
			DisplayObject(this.buttons).name = 'buttons';
			this.buttons.form = this; 
			this.buttons.editable = this.editable;
			
			var o:DisplayObject = this.getChildByName('buttons');
			if (o) this.removeChild(o);
			this.addChild(DisplayObject(this.buttons));
		}
	
		protected function validateForm(event:Event=null) : void
		{
			for ( var i:int=0, j:int=nonNillableControls.length; i<j; i++)
			{
				var control: DisplayObject = DisplayObject(nonNillableControls[i]);
				
				if ( 
					(control is ComboBox && ComboBox(control).selectedIndex < 0 ) ||
					(control is DateField && DateField(control).selectedDate == null ) ||
					(control is TextArea && ! TextArea(control).text ) ||
					(control is TextInput && ! TextInput(control).text ) ||
					(control is ReferenceInput && ReferenceInput(control).sobject == null )
				)
				{
					formIsValid = false;
					return;
				} 
			}
			formIsValid = true;
		}
	
	
		/**
		 * switch to edit mode
		 */
		private function editForm() : void
		{
			editable = true;
		}

		/**
		 * cancel out of edit mode
		 */
		private function cancelForm() : void
		{
			if (onCancel!=null)
			{
				onCancel();
				return;
			}
			
			if (sobject.Id == null)
			{
				new KfApplicationLocator().kfApplication.uiContainer.removeUiComponentContaining(this);
				return;
			}
						
		    editable = false;
		    redrawForm();
		}
		
		private function validate() : Boolean
		{
			return true;	
		}
		
		private function save() : void
		{
			if ( ! validate() ) return;
			
			var updatedSObject:Object = new Object();
			updatedSObject.Id = sobject.Id;


			// loop items and add values
		    for (var i:int = 0; i < _controls.length; i++)
		    {
		    	var formControl:Object = _controls[i];
		        
				var value:String = formUtil.getSfValueFromFormControl(formControl);
	        	updatedSObject[formControl.name] = value;

		    }
			
			enabled = false;
			
			sobject.__model.saveObject(sobject.__type, updatedSObject,
				function (result:ISObject) : void
				{
					if ( updatedSObject.Id == null)
					{
						sobject = result;
					}
					enabled = true;
					editable = false;
					redrawForm();
				},
				function (error:*) : void
				{
					enabled = true;
					
					if (_statusMessage == null)
					{
						_statusMessage = new Text();
						_container.addChildAt(_statusMessage, 0);
					}
					
					_statusMessage.text = 'There has been an error, cannot save.';
					
					if (error is Fault)
					{
						
					}
					else if (error is ArrayCollection)
					{
						// array collection of com.salesforce.results.Error
					}
					else
					{
						// unspecified error
					}
				}  
			);

		}
			

			
		private function deleteAlert() : void
		{
			Alert.show("Are you sure?", "Alert", Alert.OK | Alert.CANCEL, this, 
				function(eventObj:CloseEvent):void 
				{ 
					if (eventObj.detail==Alert.OK) 
					{
						sobject.__model.deleteObject(sobject,
							function (result:*) : void
							{
								if (onDelete!=null) onDelete(sobject);
							},
							function (error:*) : void
							{
								Alert.show('Cannot delete this object.');
							}
						);
						
						
					}
				},
				null, Alert.OK);
		}
	

	
		private function maximizeWidth(o:Object) : void
		{
			o.percentWidth = 100;
		}
		
	}
}